<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2021/2/21
 * Time: 15:14
 */
declare(strict_types=1);

namespace App\Service;
use App\Model\Dao\MchAccountAccessDao;
use App\Model\Dao\MchAccountAccessDetailDao;
use App\Model\Dao\MchAccountDao;
use App\Model\Dao\RateSettleDao;
use App\Model\Dao\TradeTransDao;
use App\Utils\CustomUtils;
use Exception;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;

/**
 * Class SettleTransService
 * @package App\Service
 * 商户转账到其他商户结算
 */
class SettleTransService extends CommonService
{

    /**
     * @Inject()
     * @var TradeTransDao
     */
    private $TradeTransDao;

    /**
     * @Inject()
     * @var MchAccountDao
     */
    private $MchAccountDao;

    /**
     * @Inject()
     * @var MchAccountAccessDao
     */
    private $MchAccountAccessDao;

    /**
     * @Inject()
     * @var MchAccountAccessDetailDao
     */
    private $MchAccountAccessDetailDao;

    /**
     * @Inject()
     * @var RateSettleDao
     */
    private $RateSettleDao;

    public function handle($param) {
        // 校验
        $trans = $this->TradeTransDao->get($param['transId']);
        if(!$trans || $trans['type'] == 1) {
            throw new Exception('转账单不存在，终止处理', FAILED_CODE);
        }
        if($trans['handleStatus'] != $param['handleStatus']) {
            throw new Exception('转账单处理状态不对应', FAILED_CODE);
        }
        if($trans['settleStatus'] == 1) {
            throw new Exception('转账单结算完成', FAILED_CODE);
        }
        $mchAccount = $this->MchAccountDao->get($param['mchAccountId']);
        if(!$mchAccount) {
            throw new Exception('查询账户记录失败', FAILED_CODE);
        }
        $isDetail = $this->MchAccountAccessDetailDao->getWhere([
            "transId"    => $trans['id'],
            "type"       => TYPE_TRANS_CASH_OUT,
        ]);

        if ($param['handleStatus'] == 0) {
            if ($isDetail && empty($isDetail['accessId'])) {
                throw new Exception('转账单已冻结结算', FAILED_CODE);
            }
            return $this->handleAdd($param, $trans, $mchAccount);
        } else {

            // 初审通过不用管
            if ($param['handleStatus'] == 1) {
                return $this->handleFirstConfirm($trans);
            }

            if ($trans['extraType'] == 2) {
                $targetMchAccount = $this->MchAccountDao->getByMchId($trans['targetMchId']);
                if(!$targetMchAccount) {
                    throw new Exception('目标账户记录失败', FAILED_CODE);
                }
            }

            return $this->handleConfirm($param, $trans, $mchAccount, $targetMchAccount);
        }
    }

    private function handleFirstConfirm($trans) {
        $this->TradeTransDao->set($trans['id'], [
            'settleStatus'  => 1,
        ]);
        return CustomUtils::xeServiceReturnData(SUCCESS_CODE, "转账单初审通过结算成功");
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @return array
     * @throws Exception
     * 新增转账记录，出入账
     */
    public function handleAdd($param, $trans, $mchAccount) {

        // 乐观锁版本号+1
        $updateVersion = $mchAccount['updateVersion'] + 1;
        $transAmount = $trans['transAmount'];

        // 交易可转账金额 余额前后
        $beforeTradeCashAmount = $mchAccount['tradeCashAmount'];
        $afterTradeCashAmount  = $mchAccount['tradeCashAmount'] - $transAmount;

        // 事件待审核冻结金额 余额前后
        $beforeEventReviewFrozenAmount = $mchAccount['eventReviewFrozenAmount'];
        $afterEventReviewFrozenAmount = $mchAccount['eventReviewFrozenAmount'] + $transAmount;

        // 开启事务
        Db::beginTransaction();
        try {
            // step
            // 1.可转账出账
            // 2.事件审核冻结入账
            // 3.更新转账单状态
            // 4.更新账户
            $this->handleCashOutTransAmount($param, $trans, $mchAccount, $transAmount, $beforeTradeCashAmount, $afterTradeCashAmount);

            $this->handleFrozenInTransAmount($param, $trans, $mchAccount, $transAmount, $beforeEventReviewFrozenAmount, $afterEventReviewFrozenAmount);

            $this->TradeTransDao->set($trans['id'], [
                'settleStatus'  => 1,
            ]);

            $updateMchAccount = array(
                "tradeCashAmount"           => $afterTradeCashAmount,
                "eventReviewFrozenAmount"   => $afterEventReviewFrozenAmount,
                "updateVersion"             => $updateVersion
            );
            $result = $this->MchAccountDao->updateWhere([
                "id" => $param['mchAccountId'],
                "updateVersion" => $mchAccount['updateVersion']
            ], $updateMchAccount);

            // 如果更新账户失败，重试，最多3次
            if (!$result) {
                throw new \Exception("更新账户失败");
            }

        } catch (\Throwable $e) {
            CustomUtils::xzxDebug($e->getMessage());
            Db::rollBack();

            // 失败重试，最多3次
            $this->tryOneMore($param);

            return CustomUtils::xeServiceReturnData(FAILED_CODE, "转账单结算失败");
        }
        Db::commit();

        return CustomUtils::xeServiceReturnData(SUCCESS_CODE, "转账单结算成功");
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * 提交记录，转账金额，可转账出账
     */
    private function handleCashOutTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accountType"       => ACCOUNT_TYPE_CASH,
            "inOutType"         => DETAIL_TYPE_OUT,
            "type"              => TYPE_TRANS_CASH_OUT,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => -$amount,
            "currency"          => $trans['transCurrency'],
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "remark"            => "转账单可转账账户出账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * 提交记录，转账金额，冻结金额入账
     */
    private function handleFrozenInTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accountType"       => ACCOUNT_TYPE_EVENT_REVIEW,
            "inOutType"         => DETAIL_TYPE_IN,
            "type"              => TYPE_TRANS_FROZEN_IN,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => $amount,
            "currency"          => $trans['transCurrency'],
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "remark"            => "转账单事件审核冻结账户入账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $targetMchAccount
     * @return array
     * @throws Exception
     * 转账，审核，出入账
     */
    private function handleConfirm($param, $trans, $mchAccount, $targetMchAccount) {
        // 乐观锁版本号+1
        $updateVersion = $mchAccount['updateVersion'] + 1;
        $transAmount = $trans['transAmount'];

        // 交易可转账金额，转账金额，余额前后
        $beforeTradeCashAmount = $mchAccount['tradeCashAmount'];
        $afterTradeCashAmount  = $mchAccount['tradeCashAmount'] + $transAmount;

        // 事件审核冻结金额，转账金额，余额前后
        $beforeEventReviewFrozenAmount = $mchAccount['eventReviewFrozenAmount'];
        $afterEventReviewFrozenAmount = $mchAccount['eventReviewFrozenAmount'] - $transAmount;

        // 交易可转账金额出账，转账金额，余额前后
        $beforeOutTradeCashAmount = $afterTradeCashAmount;
        $afterOutTradeCashAmount  = $afterTradeCashAmount - $transAmount;

        // 收账可转账金额入账，转账金额，余额前后
        $targetAmount = $trans['targetAmount'];
        $beforeInTargetTradeCashAmount = $targetMchAccount['tradeCashAmount'];
        $afterInTargetTradeCashAmount  = $targetMchAccount['tradeCashAmount'] + $targetAmount;

        // 交易可转账金额，扣转账手续费，余额前后
        $transPerFee = 0;
        $beforeWithdrawPerFeeTradeCashAmount = 0;
        $afterWithdrawPerFeeTradeCashAmount  = 0;
        if ($param['handleStatus'] == 3) {
            $rateSettle = $this->RateSettleDao->getByMchId($trans['mchId']);
            if(!$rateSettle) {
                throw new Exception('查询商户结算费率失败', FAILED_CODE);
            }
            $transPerFee = $rateSettle['transPerFee'];
            $beforeWithdrawPerFeeTradeCashAmount = $afterOutTradeCashAmount;
            $afterWithdrawPerFeeTradeCashAmount = $beforeWithdrawPerFeeTradeCashAmount - $transPerFee;
        }

        // 转账总金额
        $tradeTransAmount  = $mchAccount['tradeTransAmount'] + $transAmount;

        // 开启事务
        Db::beginTransaction();
        try {
            // step
            // 1.事件审核冻结账户出账
            // 2.转账可转账账户入账
            // 3.审核成功，可转账账户出账
            // 4.审核成功，可转账账户手续费出账
            // 5.审核成功，目标商户交易可提现账户入账
            // 6.更新转账结算状态
            // 7.更新账户

            // step 1
            $accessId = $this->handleConfirmFrozenOutTransAmount($param, $trans, $mchAccount, $transAmount, $beforeEventReviewFrozenAmount, $afterEventReviewFrozenAmount);

            // handleAdd新增时添加的记录，查询
            $frozenInDetail = $this->MchAccountAccessDetailDao->getWhere(['transId'  => $trans['id'], 'type' => TYPE_TRANS_FROZEN_IN]);

            $this->MchAccountAccessDetailDao->update($frozenInDetail['id'], ['accessId' => $accessId]);

            // step 2
            $confirmCashInId = $this->handleConfirmCashInTransAmount($param, $trans, $mchAccount, $transAmount, $beforeTradeCashAmount, $afterTradeCashAmount);

            if ($param['handleStatus'] == 3) {
                // step 3
                $accessId = $this->handleConfirmCashOutTransAmount($param, $trans, $mchAccount, $transAmount, $beforeOutTradeCashAmount, $afterOutTradeCashAmount);

                $this->MchAccountAccessDetailDao->update($confirmCashInId, ['accessId' => $accessId]);

                // handleAdd新增时添加的记录，查询
                $cashOutDetail = $this->MchAccountAccessDetailDao->getWhere(['transId'  => $trans['id'], 'type' => TYPE_TRANS_CASH_OUT]);

                $this->MchAccountAccessDetailDao->update($cashOutDetail['id'], ['accessId' => $accessId]);

                // step 4
                $this->handleConfirmTransPerFee($param, $trans, $mchAccount, $transPerFee, $beforeWithdrawPerFeeTradeCashAmount, $afterWithdrawPerFeeTradeCashAmount);

                if ($trans['extraType'] == 2) {
                    // 如果是平台内账户互转，才会给目标账户转入金额 step 5
                    $this->handleConfirmCashInTargetTransAmount($param, $trans, $targetMchAccount, $targetAmount, $beforeInTargetTradeCashAmount, $afterInTargetTradeCashAmount);

                    $updateTargetMchAccount = array(
                        "tradeCashAmount"       => $afterInTargetTradeCashAmount,
                    );
                    $this->MchAccountDao->updateWhere([
                        "id"            => $targetMchAccount['id'],
                        "updateVersion" => $targetMchAccount['updateVersion']
                    ], $updateTargetMchAccount);
                }
            }

            // 审核成功
            if ($param['handleStatus'] == 3) {
                // step 6
                $this->TradeTransDao->set($trans['id'], [
                    'settleStatus'  => 1,
                    'settleTime'    => time(),
                    'transFee'      => $transPerFee
                ]);

                // step 7
                $updateMchAccount = array(
                    "tradeCashAmount"           => $afterWithdrawPerFeeTradeCashAmount,
                    "eventReviewFrozenAmount"   => $afterEventReviewFrozenAmount,
                    "tradeTransAmount"          => $tradeTransAmount,
                    "updateVersion"             => $updateVersion
                );
            } else {
                // step 6
                $this->TradeTransDao->set($trans['id'], [
                    'settleStatus'  => 1,
                    'settleTime'    => time()
                ]);

                // step 7
                $updateMchAccount = array(
                    "tradeCashAmount"           => $afterTradeCashAmount,
                    "eventReviewFrozenAmount"   => $afterEventReviewFrozenAmount,
                    "updateVersion"             => $updateVersion
                );
            }
            $result = $this->MchAccountDao->updateWhere([
                "id" => $param['mchAccountId'],
                "updateVersion" => $mchAccount['updateVersion']
            ], $updateMchAccount);

            // 如果更新账户失败，重试，最多3次
            if (!$result) {
                throw new \Exception("更新账户失败");
            }

        } catch (\Throwable $e) {
            CustomUtils::xzxDebug($e->getMessage());
            Db::rollBack();

            // 失败重试，最多3次
            $this->tryOneMore($param);

            return CustomUtils::xeServiceReturnData(FAILED_CODE, "交易转账审核结算失败");
        }
        Db::commit();

        return CustomUtils::xeServiceReturnData(SUCCESS_CODE, "交易转账审核结算成功");
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * @return string
     * 审核，转账金额，可转账入账
     */
    private function handleConfirmCashInTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {
        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accountType"       => ACCOUNT_TYPE_CASH,
            "inOutType"         => DETAIL_TYPE_IN,
            "type"              => TYPE_TRANS_CONFIRM_CASH_IN,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => $amount,
            "currency"          => $trans['transCurrency'],
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "remark"            => "转账单可转账账户入账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
        return $mchAccountAccessDetail['id'];
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * @return string
     * 审核，转账金额，可转账出账，转账成功
     */
    private function handleConfirmCashOutTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {
        // 写主表
        $mchAccountAccess = array(
            "id"           => CustomUtils::createUUID(),
            "mchId"        => $trans["mchId"],
            "mchNo"        => $trans["mchNo"],
            "mchAccountId" => $param['mchAccountId'],
            "accessType"   => ACCESS_TYPE_TRANS,
            "accountType"  => ACCOUNT_TYPE_CASH,
            "transId"      => $trans['id'],
            "transNo"      => $trans['transNo'],
            "amount"       => -$amount,
            "currency"     => $trans['transCurrency'],
            "status"       => 1,
            "remark"       => "转账单转账",
            "createTime"   => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDao->add($mchAccountAccess);

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accessId"          => $mchAccountAccess['id'],
            "accountType"       => ACCOUNT_TYPE_CASH,
            "inOutType"         => DETAIL_TYPE_OUT,
            "type"              => TYPE_TRANS_CONFIRM_CASH_OUT,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => -$amount,
            "currency"          => $trans['transCurrency'],
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "remark"            => "转账单转账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
        return $mchAccountAccess['id'];
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * @return string
     * 确认，转账金额，解冻账户出账
     */
    private function handleConfirmFrozenOutTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {
        // 写主表
        $mchAccountAccess = array(
            "id"           => CustomUtils::createUUID(),
            "mchId"        => $trans["mchId"],
            "mchNo"        => $trans["mchNo"],
            "mchAccountId" => $param['mchAccountId'],
            "accessType"   => ACCESS_TYPE_TRANS,
            "accountType"  => ACCOUNT_TYPE_EVENT_REVIEW,
            "transId"      => $trans['id'],
            "transNo"      => $trans['transNo'],
            "amount"       => 0,
            "currency"     => $trans['transCurrency'],
            "status"       => 1,
            "isShow"       => 0,
            "remark"       => "转账单解冻",
            "createTime"   => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDao->add($mchAccountAccess);

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accessId"          => $mchAccountAccess['id'],
            "accountType"       => ACCOUNT_TYPE_EVENT_REVIEW,
            "inOutType"         => DETAIL_TYPE_OUT,
            "type"              => TYPE_TRANS_CONFIRM_FROZEN_OUT,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => -$amount,
            "currency"          => $trans['transCurrency'],
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "remark"            => "转账单事件审核冻结账户解冻出账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
        return $mchAccountAccess['id'];
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * 转账手续费出账
     */
    private function handleConfirmTransPerFee($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {

        // 写主表
        $mchAccountAccess = array(
            "id"           => CustomUtils::createUUID(),
            "mchId"        => $trans["mchId"],
            "mchNo"        => $trans["mchNo"],
            "mchAccountId" => $param['mchAccountId'],
            "accessType"   => ACCESS_TYPE_TRANS,
            "accountType"  => ACCOUNT_TYPE_CASH,
            "transId"      => $trans['id'],
            "transNo"      => $trans['transNo'],
            "amount"       => -$amount,
            "currency"     => $trans['transCurrency'],
            "status"       => 1,
            "remark"       => "转账单手续费费出账",
            "createTime"   => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDao->add($mchAccountAccess);

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["mchId"],
            "mchNo"             => $trans["mchNo"],
            "mchAccountId"      => $param['mchAccountId'],
            "accessId"          => $mchAccountAccess['id'],
            "accountType"       => ACCOUNT_TYPE_CASH,
            "inOutType"         => DETAIL_TYPE_OUT,
            "type"              => TYPE_TRANS_CONFIRM_TRANS_PER_FEE_OUT,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => -$amount,
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "currency"          => $trans['transCurrency'],
            "remark"            => "转账单手续费费出账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
    }

    /**
     * @param $param
     * @param $trans
     * @param $mchAccount
     * @param $amount
     * @param $beforeAmount
     * @param $afterAmount
     * 目标账户入账
     */
    private function handleConfirmCashInTargetTransAmount($param, $trans, $mchAccount, $amount, $beforeAmount, $afterAmount) {

        $targetMchAccount = $this->MchAccountDao->getByMchId($trans["targetMchId"]);

        // 写主表
        $mchAccountAccess = array(
            "id"           => CustomUtils::createUUID(),
            "mchId"        => $trans["targetMchId"],
            "mchNo"        => $trans["targetMchNo"],
            "mchAccountId" => $targetMchAccount['id'],
            "accessType"   => ACCESS_TYPE_TRANS,
            "accountType"  => ACCOUNT_TYPE_CASH,
            "transId"      => $trans['id'],
            "transNo"      => $trans['transNo'],
            "amount"       => $amount,
            "currency"     => $trans['targetCurrency'],
            "status"       => 1,
            "remark"       => "转账目标账户交易可提现账户入账",
            "createTime"   => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDao->add($mchAccountAccess);

        // 写明细表
        $mchAccountAccessDetail = array(
            "id"                => CustomUtils::createUUID(),
            "mchId"             => $trans["targetMchId"],
            "mchNo"             => $trans["targetMchNo"],
            "mchAccountId"      => $targetMchAccount['id'],
            "accessId"          => $mchAccountAccess['id'],
            "accountType"       => ACCOUNT_TYPE_CASH,
            "inOutType"         => DETAIL_TYPE_IN,
            "type"              => TYPE_TRANS_CONFIRM_TARGET_CASH_IN,
            "beforeTotalAmount" => $beforeAmount,
            "afterTotalAmount"  => $afterAmount,
            "amount"            => $amount,
            "transId"           => $trans['id'],
            "transNo"           => $trans['transNo'],
            "currency"          => $trans['targetCurrency'],
            "remark"            => "转账目标账户交易可提现账户入账",
            "createTime"        => CustomUtils::xzxGetMsecTime()
        );
        $this->MchAccountAccessDetailDao->add($mchAccountAccessDetail);
    }
}